use std::path::Path;

use abi_stable::{
    library::{LibraryError, RootModule},
    package_version_strings, sabi_trait,
    sabi_types::VersionStrings,
    std_types::{RBox, RString, RVec},
    DynTrait, StableAbi,
};

/// This struct is the root module,
/// which must be converted to `ExampleLib_Ref` to be passed through ffi.
///
/// The `#[sabi(kind(Prefix(prefix_ref = ExampleLib_Ref)))]`
/// attribute tells `StableAbi` to create an ffi-safe static reference type
/// for `ExampleLib` called `ExampleLib_Ref`.
///
/// The `#[sabi(missing_field(panic))]` attribute specifies that trying to
/// access a field that doesn't exist must panic with a message saying that
/// the field is inaccessible.
#[repr(C)]
#[derive(StableAbi)]
#[sabi(kind(Prefix(prefix_ref = ExampleLib_Ref)))]
#[sabi(missing_field(panic))]
pub struct ExampleLib {
    pub new_appender: extern "C" fn() -> AppenderBox<u32>,

    pub new_boxed_interface: extern "C" fn() -> BoxedInterface<'static>,

    /// The `#[sabi(last_prefix_field)]` attribute here means that this is the last
    /// field in this struct that was defined in the first compatible version of the library
    /// (0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc),
    /// requiring new fields to always be added below preexisting ones.
    ///
    /// The `#[sabi(last_prefix_field)]` attribute would stay on this field until the
    /// library bumps its "major" version,
    /// at which point it would be moved to the last field at the time.
    ///
    #[sabi(last_prefix_field)]
    pub append_string: extern "C" fn(&mut BoxedInterface<'_>, RString),
}

/// The RootModule trait defines how to load the root module of a library.
impl RootModule for ExampleLib_Ref {
    abi_stable::declare_root_module_statics! {ExampleLib_Ref}

    const BASE_NAME: &'static str = "example_library";
    const NAME: &'static str = "example_library";
    const VERSION_STRINGS: VersionStrings = package_version_strings!();
}

/// This loads the root from the library in the `directory` folder.
pub fn load_root_module_in_directory(directory: &Path) -> Result<ExampleLib_Ref, LibraryError> {
    ExampleLib_Ref::load_from_directory(directory)
}

//////////////////////////////////////////////////////////

/// `#[sabi_trait]` is how one creates an ffi-safe trait object from a trait definition.
///
/// In this case, the trait object is `Appender_TO<'lt, Pointer<()>, Element>`,where:
///
/// - `'lt`:
///     Is the lifetime bound of the type that constructed the trait object
///     (`'static` is the lifetime bound of objects that don't borrow anything).
///
/// - `Pointer<()>`:
///     Is any pointer that implements some abi_stable specific traits,
///     this pointer owns the value that implements `Appender`.
///
/// - `Element`:
///     This is the element type of the collection that we operate on.
///     This is a type parameter because it's a trait object,
///     which turn associated types into type parameters.
///
#[sabi_trait]
pub trait Appender {
    /// The element type of the collection.
    type Element;

    /// Appends one element at the end of the collection.    
    fn push(&mut self, value: Self::Element);

    /// Appends many elements at the end of the collection.    
    fn append(&mut self, vec: RVec<Self::Element>);

    /// Converts this collection into an `RVec`.
    ///
    /// As opposed to regular trait objects,
    /// it is possible to call by-value methods on trait objects generated by `#[sabi_trait]`.
    ///
    /// The `#[sabi(last_prefix_field)]` attribute here means that this is the last method
    /// that was defined in the first compatible version of the library
    /// (0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc),
    /// requiring new methods to always be added below preexisting ones.
    ///
    /// The `#[sabi(last_prefix_field)]` attribute would stay on this method until the library
    /// bumps its "major" version,
    /// at which point it would be moved to the last method at the time.
    ///
    #[sabi(last_prefix_field)]
    fn into_rvec(self) -> RVec<Self::Element>;
}

/// A type alias for the Appender trait object.
///
/// `'static` here means that the trait object cannot contain any borrows.
pub type AppenderBox<T> = Appender_TO<'static, RBox<()>, T>;

// Impls of local traits for dependencies have to be implemented in
// the interface crate, because of the orphan rules.
//
// To avoid compiling more code than necessary,
// this impl is not compiled by default.
// it's enabled by the implementation crate but not the user crate.
#[cfg(feature = "impls")]
impl<T> Appender for RVec<T> {
    type Element = T;
    fn push(&mut self, value: Self::Element) {
        self.push(value);
    }
    fn append(&mut self, vec: RVec<Self::Element>) {
        self.extend(vec);
    }
    fn into_rvec(self) -> RVec<Self::Element> {
        self
    }
}

//////////////////////////////////////////////////////////

/// This type implements `ÌnterfaceType`
/// (because of the `#[sabi(impl_InterfaceType())]` helper attribute of `#[derive(StableAbi)]` ),
/// describing the traits required when constructing `DynTrait<_, TheInterface>`,
/// and are then implemented by it.
#[repr(C)]
#[derive(StableAbi)]
#[sabi(impl_InterfaceType(Sync, Send, Debug, Display))]
pub struct TheInterface;

/// An alias for the trait object used in this example
pub type BoxedInterface<'borr> = DynTrait<'borr, RBox<()>, TheInterface>;
